package com.xzx.redisstudy.lock;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

import java.util.Arrays;
import java.util.UUID;

/**
 * 测试分布式锁
 *
 * @author xinzhixuan
 * @version 1.0
 * @date 2021-06-01 11:42 下午
 */
public class TestLock {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        demoV4(jedis);
        jedis.close();
    }

    /**
     * v1版本的问题在于，如果业务异常，锁就永远不会释放，
     * @param jedis .
     */
    private static void demoV1(Jedis jedis) {
        Long lock = jedis.setnx("lock", "1");
        if (lock == 1) { //获取锁成功
            // 执行业务逻辑
//            jedis.set("num", "a");
            jedis.incr("num");
            // 释放锁
            jedis.del("lock");
        } else {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            demoV1(jedis);
        }
    }

    /**
     * v2版本,为锁加了超时时间，防止锁永远不释放的问题
     * 新的问题在于，可能释放锁时释放的是别人的锁，场景是，执行业务耗时100秒，但是锁只有10秒就过期了，再del时，可能会删掉其他事物的锁
     * 解决问题的方式时，不同的事物使用不通的锁id标识下
     * @param jedis .
     */
    private static void demoV2(Jedis jedis) {
        SetParams setParams = new SetParams();
        setParams.nx().ex(10);
        String lock = jedis.set("lock", "1", setParams);
        if (lock != null) { //获取锁成功
            // 执行业务逻辑
            jedis.set("num", "a");
            jedis.incr("num");
            // 释放锁
            jedis.del("lock");
        } else {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            demoV2(jedis);
        }
    }

    /**
     * v4版本终极分布式锁
     * @param jedis .
     */
    private static void demoV4(Jedis jedis) {
        String uuid = UUID.randomUUID().toString();
        SetParams setParams = new SetParams();
        setParams.nx().ex(10);
        String lock = jedis.set("lock", uuid, setParams);
        if (lock != null) { //获取锁成功
            // 执行业务逻辑
//            jedis.set("num", "a");
            jedis.incr("num");
            // 释放锁前校验下是不是自己加的锁
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            jedis.eval(script, Arrays.asList("lock"), Arrays.asList(uuid));
        } else {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            demoV4(jedis);
        }
    }

    /**
     * v3版本的问题，是校验锁和del锁不是原子性
     * @param jedis .
     */
    private static void demoV3(Jedis jedis) {
        String uuid = UUID.randomUUID().toString();
        SetParams setParams = new SetParams();
        setParams.nx().ex(10);
        String lock = jedis.set("lock", uuid, setParams);
        if (lock != null) { //获取锁成功
            // 执行业务逻辑
            jedis.set("num", "a");
            jedis.incr("num");
            // 释放锁前校验下是不是自己加的锁
            String lockId = jedis.get("lock");
            if (uuid.equals(lockId)) {
                jedis.del("lock");
            }
        } else {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            demoV3(jedis);
        }
    }
}
